<?php

declare (strict_types = 1);

namespace auth;

use auth\contract\Authenticatable;
use auth\contract\UserProvider;
use hashing\contract\Hasher;
use support\Str;
use think\db\BaseQuery;
use think\Model;

class OrmUserProvider implements UserProvider
{
    /**
     * @var \hashing\contract\Hasher
     */
    protected $hasher;

    /**
     * 用户模型
     *
     * @var string
     */
    protected $model;

    /**
     * @var string
     */
    protected $tokenableType;

    public function __construct(Hasher $hasher, string $model)
    {
        $this->hasher = $hasher;
        $this->model = $model;
        $this->tokenableType = ltrim($this->model, '\\');
    }

    /**
     * 根据主键提取认证用户
     *
     * @param $identifier
     * @return \auth\contract\Authenticatable|null
     *
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function retrieveById($identifier): ?Authenticatable
    {
        $query = $this->newModelQuery();
        $model = $this->createModel();

        return $query->where($model->getAuthIdentifierName(), $identifier)->find();
    }

    /**
     * 根据 token 提取认证用户
     *
     * @param  AccessToken  $token
     * @return \auth\contract\Authenticatable|null
     *
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function retrieveByToken(AccessToken $token): ?Authenticatable
    {
        if ($token->tokenable_type !== $this->tokenableType) {
            return null;
        }

        $model = $this->createModel();

        $retrievedModel = $this->newModelQuery($model)->where(
            $model->getAuthIdentifierName(),
            $token->tokenable_id
        )->find();

        return $retrievedModel ?: null;
    }

    /**
     * 根据账号信息从数据库中提取用户
     *
     * @param  array  $credentials
     * @return \auth\contract\Authenticatable|null
     *
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function retrieveByCredentials(array $credentials): ?Authenticatable
    {
        $query = $this->newModelQuery();
        foreach ($credentials as $key => $value) {
            if (Str::contains($key, 'password')) {
                continue;
            }

            $query->where($key, $value);
        }

        return $query->find();
    }

    /**
     * 核验用户凭证
     *
     * @param  \auth\contract\Authenticatable  $user
     * @param  array  $credentials
     * @return bool
     */
    public function validateCredentials(Authenticatable $user, array $credentials): bool
    {
        $plain = $credentials['password'];

        return $this->hasher->check($plain, $user->getAuthPassword());
    }

    /**
     * @param  \think\Model|null  $model
     * @return \think\db\BaseQuery
     */
    protected function newModelQuery(Model $model = null): BaseQuery
    {
        return is_null($model)
            ? $this->createModel()->newQuery()
            : $model->newQuery();
    }

    /**
     * @return \think\Model
     */
    protected function createModel(): Model
    {
        $class = '\\'.$this->tokenableType;

        return new $class;
    }
}
